﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Collections;

using Cards.GUI;
using Cards.Util;

namespace Cards.Objects
{
    /// <summary>
    /// Multi game server class
    /// </summary>
    public class GameServer : MultiGame
    {
        private int m_maxClient;                    /// Max player in the game
        private Socket m_serverSocket;              /// Server socket
        private Hashtable m_hashClients;            /// Clients hash map
        public AsyncCallback m_pFnClientCallBack;   /// Thread to listen asynchronously call back

        public Socket SERVER
        {
            get { return m_serverSocket; }
            set { m_serverSocket = value; }
        }
        public int MAXCLIENT
        {
            get { return m_maxClient; }
            set { m_maxClient = value; }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="_parentForm"></param>
        /// <param name="_maxClient"></param>
        public GameServer(FrmCreateMultiGame _parentForm, int _maxClient) : base(_parentForm)
        {
            m_maxClient = _maxClient;
            m_hashClients = new Hashtable(_maxClient);
        }

        /// <summary>
        /// Start
        /// </summary>
        /// <returns></returns>
        public override Boolean Start()
        {
            return StartServer();
        }

        /// <summary>
        /// Stop
        /// </summary>
        public override void Stop()
        {
            StopServer();
        }

        /// <summary>
        /// Server broadcast message
        /// </summary>
        /// <param name="_str"></param>
        public override void SendMessage(String _str)
        {
            BroadcastMessage(_str);
        }

        /// <summary>
        /// Send a message to a specified client
        /// </summary>
        /// <param name="_str"></param>
        /// <param name="_client"></param>
        public void SendMessage(String _str, Socket _client)
        {
            try
            {
                Object objData = _str;
                byte[] byData = System.Text.Encoding.ASCII.GetBytes(objData.ToString());

                if (_client != null)
                {
                    if (_client.Connected)
                    {
                        _client.Send(byData);
                    }
                }

            }
            catch (SocketException se)
            {
                m_frmParentMultiGame.UpdateMsg(se.Message);
            }
        }

        /// <summary>
        /// Start server listening
        /// </summary>
        /// <returns></returns>
        public Boolean StartServer()
        {
            try
            {
                if (m_port == "")
                {
                    m_frmParentMultiGame.UpdateMsg("Please enter a Port Number");
                    return false;
                }

                int port = System.Convert.ToInt32(m_port);

                m_serverSocket = new Socket(AddressFamily.InterNetwork,
                                            SocketType.Stream,
                                            ProtocolType.Tcp);
                IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);

                m_serverSocket.Bind(ipLocal);
                m_serverSocket.Listen(4);

                m_serverSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);

                return true;
            }
            catch (SocketException se)
            {
                m_frmParentMultiGame.UpdateMsg(se.Message);
                return false;
            }
        }

        /// <summary>
        /// Thread to receive a client connection
        /// </summary>
        /// <param name="asyn"></param>
        public void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                if (m_hashClients.Count >= m_maxClient)
                {
                    Socket socket;

                    socket = m_serverSocket.EndAccept(asyn);
                    SendMessage("Phong choi da du nguoi", socket);
                    socket.Close();
                    socket = null;

                    //Waiting for client again
                    m_serverSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);

                    return;
                }

                SocketPacket socPacket = new SocketPacket();

                // Finished accepting connection
                socPacket.m_currentSocket = m_serverSocket.EndAccept(asyn);

                socPacket.m_id = m_frmParentMultiGame.GetAvailableClientId();

                if (socPacket.m_id == -1)
                {
                    SendMessage("Phong choi da du nguoi", socPacket.m_currentSocket);
                    socPacket.m_currentSocket.Close();
                    socPacket.m_currentSocket = null;
                    return;
                }


                // Add client socket packet to hash
                m_hashClients.Add(socPacket.m_id, socPacket);

                WaitForData(socPacket.m_currentSocket, socPacket);

                String str = String.Format("Player {0} connected\n", socPacket.m_id);
                m_frmParentMultiGame.UpdateTestText(str);

                //Waiting for client again
                m_serverSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\n OnClientConnection: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                m_frmParentMultiGame.UpdateMsg(se.Message);
            }
            catch (SystemException se)
            {
                MessageBox.Show(se.Message);
            }
        }

        /// <summary>
        /// Start receiving data from a client
        /// </summary>
        /// <param name="soc"></param>
        /// <param name="socPacket"></param>
        public void WaitForData(Socket soc, SocketPacket socPacket)
        {
            try
            {
                if (m_pFnClientCallBack == null)
                {
                    m_pFnClientCallBack = new AsyncCallback(OnDataReceived);
                }

                // start receive data from the soc
                soc.BeginReceive(socPacket.m_dataBuffer, 0,
                                 socPacket.m_dataBuffer.Length,
                                 SocketFlags.None,
                                 m_pFnClientCallBack,
                                 socPacket);
            }
            catch (SocketException se)
            {
                //MessageBox.Show(se.Message);
                m_frmParentMultiGame.UpdateMsg(se.Message);
            }

        }

        /// <summary>
        /// Thread listening to client data
        /// </summary>
        /// <param name="asyn"></param>
        public void OnDataReceived(IAsyncResult asyn)
        {
            SocketPacket socketData = (SocketPacket)asyn.AsyncState;
            int iRx = 0;

            try
            {
                // Complete the BeginReceive() asynchronous call by EndReceive() method
                // which will return the number of characters written to the stream 
                // by the client
                iRx = socketData.m_currentSocket.EndReceive(asyn);
                
                char[] chars = new char[iRx];
                
                Decoder d = Encoding.UTF8.GetDecoder();

                int charLen = d.GetChars(socketData.m_dataBuffer, 0, iRx, chars, 0);

                String szData = new System.String(chars);

                testTxt += szData;
                testTxt = String.Format("Nguoi choi {0} gui: ", socketData.m_id) + szData;
                //m_frmParentMultiGame.UpdateTestText(testTxt);
                testTxt = "";

                ProcessMessage(socketData, szData);

                // Continue the waiting for data on the Socket
                WaitForData(socketData.m_currentSocket, socketData);
            }
            catch (ObjectDisposedException)
            {
                System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
            }
            catch (SocketException se)
            {
                m_frmParentMultiGame.UpdateMsg(String.Format("Người chơi {0} đã ngắt kết nối\n", socketData.m_id));
                m_frmParentMultiGame.UpdatePlayerName(socketData.m_id, "");

                // Broadcast player list to all players
                String msg = NetworkConstants.MESSAGETYPE_SERVER_BROADCAST_PLAYER_LIST + NetworkConstants.MESSAGETYPE_SEPARATOR;
                msg += m_frmParentMultiGame.GetStringPlayerList();
                BroadcastMessage(msg);

                // Remove socket
                m_hashClients.Remove(socketData.m_id);
                // Free the space in game
                m_frmParentMultiGame.GiveBackAvailableClientId(socketData.m_id);
            }
        }

        /// <summary>
        /// Parse the received message to recognize the command
        /// </summary>
        /// <param name="_sockPkg"></param>
        /// <param name="_message"></param>
        public void ProcessMessage(SocketPacket _sockPkg, String _message)
        {
            try
            {
                String[] msgList = _message.Split(":".ToCharArray());
                String command = msgList[0];
                int idx = 0;

                if (msgList.Length <= 0)
                {
                    return;
                }

                switch (command)
                {
                    case NetworkConstants.MESSAGETYPE_PLAYERNAME:
                        idx++;

                        if (msgList.Length > 1)
                        {
                            _sockPkg.m_name = msgList[idx];
                            m_frmParentMultiGame.UpdatePlayerName(_sockPkg.m_id, _sockPkg.m_name);

                            // Send WELCOME message to current player
                            String msg = NetworkConstants.MESSAGETYPE_SERVER_WELCOME + NetworkConstants.MESSAGETYPE_SEPARATOR + _sockPkg.m_id;
                            SendMessage(msg, _sockPkg.m_currentSocket);

                            // Broadcast player list to all players
                            msg = NetworkConstants.MESSAGETYPE_SERVER_BROADCAST_PLAYER_LIST + NetworkConstants.MESSAGETYPE_SEPARATOR;
                            msg += m_frmParentMultiGame.GetStringPlayerList();
                            BroadcastMessage(msg);
                        }

                        break;

                    case NetworkConstants.MESSAGETYPE_MOVE:
                        int id;
                        idx++;
                        id = Int32.Parse(msgList[idx]);

                        idx++;
                        String[] strCards = msgList[idx].Split(NetworkConstants.MESSAGETYPE_SUBSEPARATOR.ToCharArray());

                        int[] arrCards = new int[strCards.Length];

                        for (int i = 0; i < strCards.Length; i++)
                        {
                            arrCards[i] = Int32.Parse(strCards[i]);
                        }

                        m_frmParentMultiGame.OnReceiveMove(id, arrCards, true);

                        break;

                    case NetworkConstants.MESSAGETYPE_SKIP:
                        idx++;
                        id = Int32.Parse(msgList[idx]);

                        m_frmParentMultiGame.OnReceiveSkip(id, true);

                        break;

                    default:
                        break;
                }
            }
            catch (SystemException se)
            {
                MessageBox.Show(se.Message);
            }
        }

        /// <summary>
        /// Broadcast a message to all clients
        /// </summary>
        /// <param name="_str"></param>
        public void BroadcastMessage(String _str, int _id = -1)
        {
            try
            {
                Object objData = _str;
                byte[] byData = System.Text.Encoding.ASCII.GetBytes(objData.ToString());

                foreach (SocketPacket socketPack in m_hashClients.Values)
                {
                    if ((socketPack != null) && (socketPack.m_currentSocket != null) && (socketPack.m_id != (_id + 1)))
                    {
                        if (socketPack.m_currentSocket.Connected)
                        {
                            socketPack.m_currentSocket.Send(byData);
                        }
                    }
                }
            }
            catch (SocketException se)
            {
                m_frmParentMultiGame.UpdateMsg(se.Message);
            }
        }
        
        /// <summary>
        /// Stop server listening
        /// </summary>
        public void StopServer()
        {
            CloseSockets();
        }

        /// <summary>
        /// Close all sockets
        /// </summary>
        void CloseSockets()
        {
            try
            {
                if (m_serverSocket != null)
                {
                    m_serverSocket.Close();
                }

                foreach (SocketPacket socketPack in m_hashClients.Values)
                {
                    if ((socketPack != null) && (socketPack.m_currentSocket != null))
                    {
                        socketPack.m_currentSocket.Close();
                        socketPack.m_currentSocket = null;
                    }
                }
            }
            catch (SystemException se)
            {
                MessageBox.Show(se.Message);
            }
        }


        #region Game actions

        /// <summary>
        /// Generic send command function
        /// </summary>
        /// <param name="_command"></param>
        /// <param name="_message"></param>
        public override void SendCommand(String _command, String _message, int _id = -1)
        {
            String msg = _command + NetworkConstants.MESSAGETYPE_SEPARATOR;
            msg += _message;

            BroadcastMessage(msg, _id);
        }

        /// <summary>
        /// Send a command to all clients to start game
        /// </summary>
        /// <param name="_message"></param>
        public override void BeginGame(String _message)
        {
            SendCommand(NetworkConstants.MESSAGETYPE_SERVER_START_GAME, _message);
        }

        /// <summary>
        /// Send a command to all clients to continue game
        /// </summary>
        /// <param name="_message"></param>
        public override void ContinueGame(String _message)
        {
            SendCommand(NetworkConstants.MESSAGETYPE_SERVER_CONTINUE_GAME, _message);
        }

        /// <summary>
        /// Broadcast a move to all clients
        /// </summary>
        /// <param name="_message"></param>
        public override void SendAMove(String _message, int _id = -1)
        {
            SendCommand(NetworkConstants.MESSAGETYPE_MOVE, _message, _id);
        }

        /// <summary>
        /// Broadcast a skip action 
        /// </summary>
        /// <param name="_message"></param>
        public override void SendASkip(String _message, int _id = -1)
        {
            SendCommand(NetworkConstants.MESSAGETYPE_SKIP, _message, _id);
        }

        #endregion Game actions
    }
}
